/*
tmock: Taller Technologies mocking system
    Copyright (C) 2014  Daniel Gutson, Andres Agustin Tiraboschi, Taller Technologies

    This file is part of tmock.

    tmock is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    tmock is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with tmock.  If not, see <http://www.gnu.org/licenses/>.

    This is the main tool file.
*/

#include "gcc-plugin.h"
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "tm.h"
#include "tree.h"
#include "stringpool.h"
#include "toplev.h"
#include "basic-block.h"
#include "pointer-set.h"
#include "hash-table.h"
#include "vec.h"
#include "ggc.h"
#include "basic-block.h"
#include "tree-ssa-alias.h"
#include "internal-fn.h"
#include "gimple-fold.h"
#include "tree-eh.h"
#include "gimple-expr.h"
#include "is-a.h"
#include "gimple.h"
#include "gimple-iterator.h"
#include "tree.h"
#include "tree-pass.h"
#include "intl.h"
#include "plugin-version.h"
#include "diagnostic.h"
#include "context.h"
#include "cpplib.h"

#include "parser.h"

struct cpp_reader* __attribute__((weak)) parse_in;

Functions functions;

int plugin_is_GPL_compatible;
bool debug_print = false;
string incName = "tmock.inc";
string mainIncName = "main.inc";
string listTmockName = "list.tmock";
string incLocation;
string listTmockLocation;

inline const char* getName(const tree t)
{
    if (t != NULL_TREE)
    {
        if (DECL_NAME(t) == NULL_TREE)
            return NULL;
        return IDENTIFIER_POINTER(DECL_NAME(t));
    }
    return NULL;
}

inline void changeFunctionName(const char* nuevoNombre, tree fndef)
{
    fndef->decl_minimal.name->identifier.id.str = (const unsigned char*)nuevoNombre;
    fndef->decl_minimal.name->identifier.id.len = strlen(nuevoNombre);
}

static void start_unit(void* gcc_data, void* user_data)
{
    size_t line = 0;
    try
    {
        functions = parseMockListFile(listTmockLocation + listTmockName, line);
        writeIncMainFile(incLocation + mainIncName, functions);
        incName = incLocation + incName;
    }
    catch (string err)
    {
        cerr << "LINE " << line << ": ERROR: " << err << "\n";
        exit(EXIT_FAILURE);
    }
    catch (const char *err)
    {
        cerr << "LINE " << line << ": ERROR: " << err << "\n";
        exit(EXIT_FAILURE);
    }
    if(debug_print)
        cout << "START UNIT\n";
}

static void finish_fn(void* gcc_data, void* user_data)
{
    tree fndef = (tree)gcc_data;
    const char* cname = getName(fndef);
    if (cname != NULL)
    {
        string name = string(cname);
        FunctionToMock* ftm = getFunction(name, "", functions);
        if (ftm != NULL) /*there is a function with that name*/
        {
            if (!ftm->declared)
            {
                string output;
                output += ftm->pointer(false, false, false, "_pointer", "") + "\n";
                output += ftm->definition("static inline ", true) + "\n";
                if (debug_print)
                    cout << "\n" << output << "\n";
                try
                {
                    ofstream incFile(incName.c_str());
                    if (!incFile)
                        throw "Error creating tmock.inc";
                    incFile.write(output.c_str(), output.size());
                    incFile.close();
                    if (cpp_push_include(parse_in, incName.c_str()) == 0)
                        throw "Error inserting code";
                    ftm->declared = true;
                }
                catch (const char *err)
                {
                    cerr << "ERROR: " << err << "\n";
                    exit(EXIT_FAILURE);
                }
            }
        }
    }
}

static void finish_dl(void* gcc_data, void* user_data)
{
    tree decl = (tree)gcc_data;
    if (getName(decl) != NULL)
    {
        string name = string(getName(decl));
        FunctionToMock* ftm = getFunction(name, "", functions);
        if (ftm != NULL) /*there is a function with that name*/
        {
            if (!ftm->declared)
            {
                string output;
                output += ftm->pointer(false, false, false, "_pointer", "") + "\n";
                output += ftm->definition("static inline ", true) + "\n";
                if (debug_print)
                    cout << "\n" << output << "\n";
                try
                {
                    ofstream incFile(incName.c_str());
                    if (!incFile)
                        throw "Error creating tmock.inc";
                    incFile.write(output.c_str(), output.size());
                    incFile.close();
                    if (cpp_push_include(parse_in, incName.c_str()) == 0)
                        throw "Error inserting code";
                    ftm->declared = true;
                }
                catch (const char *err)
                {
                    cerr << "ERROR: " << err << "\n";
                    exit(EXIT_FAILURE);
                }
            }
        }
    }
}

static void getFunctionTree(void* gcc_data, void* user_data)
{
    tree fntree = (tree)gcc_data;
    const char* cname = getName(fntree);
    if (cname != NULL)
    {
        string name = cname;
        FunctionToMock* ftm = getFunction(name, "_wrapper", functions);
        if (ftm != NULL) /*there is a function that is named "name##_wrapper"*/
            /*Get the tree of the mock function for after replace it in GIMPLE*/
            ftm->fntree = fntree;
    }
}

static void processStatement(gimple stmt)
{
    string currentFunctionName = getName(current_function_decl);
    if (gimple_code(stmt) == GIMPLE_CALL)
    {
        tree calledFunction = gimple_call_fndecl(stmt);
        const char* calledFunctionName = getName(calledFunction);
        if (calledFunctionName)
        {
            string name = calledFunctionName;
            FunctionToMock* ftm = getFunction(name, "", functions);
            if (ftm != NULL)
            {
                if ((ftm->getId() == -1) || ftm->isFunctionToTest(currentFunctionName, ""))
                   /*Change the called function (in the function to test) for the mock function*/
                   gimple_call_set_fndecl(stmt, ftm->fntree);
            }
        }
    }
}

static bool
gateReplaceFunction(void) {
    return (parse_in != NULL);
}

static unsigned int executeReplaceFunction(void)
{
    gimple_stmt_iterator gsi;
    basic_block bb;
    FOR_EACH_BB_FN(bb, cfun)
    {
        for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi))
        {
            gimple stmt = gsi_stmt(gsi);
            processStatement(stmt);
        }
    }
    return 0;
}

const pass_data pass_data_replaceFunction =
{
    GIMPLE_PASS, /* type */
    "replaceFunction", /* name */
    OPTGROUP_NONE, /* optinfo_flags */
    true, /* has_gate */
    true, /* has_execute */
    TV_NONE, /* tv_id */
    PROP_ssa, /* properties_required */
    0, /* properties_provided */
    0, /* properties_destroyed */
    0, /* todo_flags_start */
    0, /* todo_flags_finish */
};

class pass_replaceFunction : public gimple_opt_pass
{
public:
    pass_replaceFunction(gcc::context* ctxt)
        : gimple_opt_pass(pass_data_replaceFunction, ctxt)
    {}

    bool gate () 
    {
        return gateReplaceFunction();
    }
    unsigned int execute()
    {
        return executeReplaceFunction();
    }
};

int plugin_init(struct plugin_name_args* plugin_info,
                struct plugin_gcc_version* version)
{
    struct register_pass_info pass_info;
    pass_info.pass = new pass_replaceFunction(g);
    pass_info.reference_pass_name = "ssa";
    pass_info.ref_pass_instance_number = 1;
    pass_info.pos_op = PASS_POS_INSERT_AFTER;
    int argc = plugin_info->argc;
    struct plugin_argument *argv = plugin_info->argv;
    for (size_t i=0;i<argc;++i)
    {
        string key = argv[i].key;
        string value = argv[i].value;
        if (key.compare("mainInc") == 0)
            incLocation = value;
        else if (key.compare("listTmock") == 0)
            listTmockLocation = value;
    }
    if (incLocation.empty())
    {
        cerr << "No location given for main.inc\n";
        exit(EXIT_FAILURE);
    }
    if (listTmockLocation.empty())
    {
        cerr << "No location given for list.tmock\n";
        exit(EXIT_FAILURE);
    }

    if (parse_in != NULL)/*if it's not in lto1*/
    {
        register_callback("start_unit", PLUGIN_START_UNIT, &start_unit, NULL);
        register_callback("finish_fn", PLUGIN_FINISH_PARSE_FUNCTION, &finish_fn, NULL);
        register_callback("finish_dl", PLUGIN_FINISH_DECL, &finish_dl, NULL);
        register_callback("getFunctionTree", PLUGIN_PRE_GENERICIZE, &getFunctionTree, NULL);
        register_callback("replaceFunction", PLUGIN_PASS_MANAGER_SETUP, NULL, &pass_info);
    }
    
    return 0;
}
